package com.rao.gmall.search.service.impl;

import com.rao.gmall.model.list.*;
import com.rao.gmall.search.dao.GoodsEsRepository;
import com.rao.gmall.search.service.SkuEsService;
import org.apache.lucene.search.join.ScoreMode;
import org.aspectj.weaver.ast.Var;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
 * @author nzmxfan
 * @create 2021-08-13-20:04
 */

/**
 * 1、ElasticsearchRepository： 专门针对Es的。 用这个  ======
 * 2、CrudRepository:  普通增删改查
 * 3、PagingAndSortingRepository： 既包含了2，还有分页
 * <p>
 * 基本的CRUD只需要给方法按照规则准备好名字
 */
@Service
public class SkuEsServiceImpl implements SkuEsService {

    @Autowired
    GoodsEsRepository goodsEsRepository;

    @Autowired
    ElasticsearchRestTemplate restTemplate;

    /**
     * 上架商品，返回 Goods 对象
     *
     * @param goods
     */
    @Override
    public void save(Goods goods) {
        goods.setCreateTime(new Date());
        goodsEsRepository.save(goods);
    }

    @Override
    public void deleteById(Long skuId) {
        goodsEsRepository.deleteById(skuId);
    }

    /**
     * 通过异步编排获取 SearchResponseVo 对象
     *
     * @param searchParam
     * @return
     */
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        // 1、根据请求参数构造出 真正的 DSL 语句（查询条件对象）
        Query query = buildQuery(searchParam);
        // 2. ES 远程检索的结果，包含了所有内容就是 ES 的返回结果
        SearchHits<Goods> goods = restTemplate.search(query, Goods.class, IndexCoordinates.of("goods"));
        // 3、把 es 的检索结果封装成 responseVo
        System.out.println(goods);
        SearchResponseVo searchResponseVo = buildResponse(goods, searchParam);
        return searchResponseVo;
    }

    /**
     * 根据查询结果构建出页面的VO
     *
     * @param responseDSL
     * @return
     */
    private SearchResponseVo buildResponse(SearchHits<Goods> responseDSL, SearchParam searchParam) {

        SearchResponseVo responseVo = new SearchResponseVo();

        // 1、构建响应的商品集合
        List<Goods> goodsList = responseVo.getGoodsList();
        // 获取桶
        List<SearchHit<Goods>> searchHits = responseDSL.getSearchHits();
        for (SearchHit<Goods> searchHit : searchHits) {
            Goods content = searchHit.getContent();
            // 获取查询到的商品
            List<String> title = searchHit.getHighlightField("title");
            if (!CollectionUtils.isEmpty(title)) {
                // 商品标题高亮显示
                content.setTitle(title.get(0));
            }
            goodsList.add(content);
        }

        // 2、构建响应的分页信息
        long totalHits = responseDSL.getTotalHits();
        responseVo.setTotal(totalHits);
        responseVo.setPageNo(searchParam.getPageNo());
        responseVo.setPageSize(searchParam.getPageSize());

        responseVo.setTotalPages((long) Math.ceil(totalHits/(searchParam.getPageSize())));

//        Long totalPages = totalHits%searchParam.getPageSize() == 0?(totalHits/searchParam.getPageSize()):(totalHits/searchParam.getPageSize() + 1);
//        responseVo.setTotalPages(totalPages);
        // 3、构建响应的品牌列表【根据聚合分析】
        List<SearchResponseTmVo> trademarkList = responseVo.getTrademarkList();
        if (responseDSL.hasAggregations()){
            // 存在聚合内容
            Aggregations aggregations = responseDSL.getAggregations();
            // 获取品牌聚合结果
            Terms tmAgg = aggregations.get("tmAgg"); // 大胆将类型改为 Terms
            // 所有品牌
            List<? extends Terms.Bucket> buckets = tmAgg.getBuckets();
            if (!CollectionUtils.isEmpty(buckets)){
                // 桶非空
                for (Terms.Bucket bucket : buckets) {
                    // 遍历品牌
                    SearchResponseTmVo tmVo = new SearchResponseTmVo();
                    // 设置品牌 id
                    tmVo.setTmId(bucket.getKeyAsNumber().longValue());
                    // 设置品牌名字
                    Terms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
                    Terms.Bucket nameBucket = tmNameAgg.getBuckets().get(0);
                    tmVo.setTmName(nameBucket.getKeyAsString());
                    // 设置品牌 url
                    Terms tmUrlAgg = nameBucket.getAggregations().get("tmUrlAgg");
                    String url = tmUrlAgg.getBuckets().get(0).getKeyAsString();
                    tmVo.setTmLogoUrl(url);
                    trademarkList.add(tmVo);
                }
            }
        }

        // 4、构建响应的属性列表【根据聚合分析】
        List<SearchResponseAttrVo> attrsList = responseVo.getAttrsList();
        if (responseDSL.hasAggregations()){
            Nested attrAgg = responseDSL.getAggregations().get("attrAgg");

            Terms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
            if (!CollectionUtils.isEmpty(attrIdAgg.getBuckets())){
                for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
                    // 遍历封装每一个属性信息
                    SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
                    // 封装属性 id
                    attrVo.setAttrId(bucket.getKeyAsNumber().longValue());
                    // 封装属性名字
                    Terms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
                    Terms.Bucket attrNameBucket = attrNameAgg.getBuckets().get(0);
                    attrVo.setAttrName(attrNameBucket.getKeyAsString());
                    // 封装属性值的可能性
                    List<String> attrValueList = attrVo.getAttrValueList();
                    Terms attrValueAgg = attrNameBucket.getAggregations().get("attrValueAgg");
                    for (Terms.Bucket valueAggBucket : attrValueAgg.getBuckets()) {
                        // 添加属性值的 1 中可能
                        attrValueList.add(valueAggBucket.getKeyAsString());
                    }
                    attrsList.add(attrVo);
                }
            }
        }

        return responseVo;
    }

    /**
     * 1、按照请求参数构建出dsl条件
     *  复杂类型的 DSL 语句查询使用 NativeSearchQueryBuilder()
     * @param searchParam
     * @return
     */
    private Query buildQuery(SearchParam searchParam) {
        // 常见查询条件对象
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();

        // 1.查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            // 根据 title 查询
            boolQuery.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()));
        }

        if (searchParam.getCategory3Id() != null) {
            // 根据 3级 id 过滤
            boolQuery.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }

        if (!StringUtils.isEmpty(searchParam.getTrademark())) {
            // 根据品牌查询  参数格式：trademark=1:小米
            String[] split = searchParam.getTrademark().split(":");
            boolQuery.filter(QueryBuilders.termQuery("tmId", split[0]));
        }

        if (searchParam.getProps() != null && searchParam.getProps().length > 0) {
            // 动态获取参数
            for (String prop : searchParam.getProps()) {
                // 参数格式：   props=407:Android:系统1&props=412:4G:内存
                String[] split = prop.split(":");
                BoolQueryBuilder query = QueryBuilders.boolQuery();

                query.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                query.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                NestedQueryBuilder attrs = QueryBuilders.nestedQuery("attrs", query, ScoreMode.None);
                boolQuery.filter(attrs);
            }
        }
        // 查询条件整合
        builder.withQuery(boolQuery);

        // 2.分页
        if (searchParam.getPageNo() != null) {
            Integer pageSize = searchParam.getPageSize();
            // 框架底层页码从 0 开始
            PageRequest of = PageRequest.of(searchParam.getPageNo() - 1, pageSize);
            builder.withPageable(of);
        }

        // 3.排序条件
        if (!StringUtils.isEmpty(searchParam.getOrder())) {
            // order=1:desc  1:按照热度排序    2:按照价格排序
            String[] split = searchParam.getOrder().split(":");
            if ("1".equals(split[0])) {
                // 按照热度排序
                FieldSortBuilder order = SortBuilders.fieldSort("hotScore").order(("desc".equals(split[1])) ? SortOrder.DESC : SortOrder.ASC);
                builder.withSort(order);
            } else {
                // 按照价格排序
                FieldSortBuilder order = SortBuilders.fieldSort("price").order(("desc".equals(split[1])) ? SortOrder.DESC : SortOrder.ASC);
                builder.withSort(order);
            }
        } else {
            // 设置默认排序方式为热度排序
            FieldSortBuilder order = SortBuilders.fieldSort("hotScore").order(SortOrder.DESC);
            builder.withSort(order);
            searchParam.setOrder("1:desc");
        }

        // 4.高亮
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            HighlightBuilder.Field field = new HighlightBuilder.Field("title");
            field.preTags("<span style='color:red'>");
            field.postTags("</span>");
            builder.withHighlightFields(field);
        }

        // 5.聚合
        // 5.1）、按照品牌聚合
        TermsAggregationBuilder tmAgg =
                AggregationBuilders.terms("tmAgg").field("tmId").size(10)
                        .subAggregation(
                                AggregationBuilders.terms("tmNameAgg").field("tmName").size(1)
                                        .subAggregation(AggregationBuilders.terms("tmUrlAgg").field("tmLogoUrl").size(1))
                        );

        builder.addAggregation(tmAgg);

        // 5.2）、按照属性聚合
        NestedAggregationBuilder nested =
                AggregationBuilders.nested("attrAgg", "attrs")
                        .subAggregation(
                                AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(100)
                                        .subAggregation(
                                                AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(1)
                                                        .subAggregation(
                                                                AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(100)
                                                        )
                                        )
                        );

        builder.addAggregation(nested);
        String s = builder.toString();
        System.out.println("DSL语句：" + s);
        return builder.build();

    }

    /**
     * 更新热度分
     *
     * @param skuId
     * @param hotScore
     */
    @Override
    public void updateSkuHotScore(Long skuId, Long hotScore) {
        Optional<Goods> byId = goodsEsRepository.findById(skuId);
        Goods goods = byId.get();
        if (goods != null){
            goods.setHotScore(hotScore);
        }
        goodsEsRepository.save(goods);
    }
}
